Die Übersichtlichkeit der Seite wird durch Javascript erhöht. Ist dies aktiviert, werden die Texte unter den Überschriften durch Anklicken der Überschriften ein- und ausgeblendet.
Ich möchte immer einen Player, der mir in großer Schrift Titel und Interpret auf dem Bildschirm anzeigt. Dadurch kann ich bequem vom Bett aus die Musik genießen und von dort aus auch die Information des gerade gespielten Titels entnehmen. Hierfür eignet sich der mplayer sehr gut. Diesen kann ich in der Konsole laufen lassen, der ich mit dem entsprechend eingerichteten Profil eine große gut aus der Entfernung lesbare Schrift verpassen kann. Leider ist der mplayer von Natur aus deutlich informationsfreudiger, so dass das wesentliche - also der gespielte Titel - in der Fülle der Information unter geht. Wie immer hatte natürlich auch hier schon jemand vor mir die Bestrebung die wichtigste Information als einzige Information auszugeben. So brauchte ich nur im Internet zu suchen und bin auf einen Konsolenbefehl gestoßen, der zum gewünschten Ergebnis führt. Selbst wäre ich da so nicht drauf gekommen. Nun möchte ich hier die Funktionsweise dieses Befehls analysieren und daraus lernen.
Konsolenbefehl (Trennung bei ')
mplayer WebAdresse 2> /dev/null | awk -F\' '/StreamTitle/ {print $2}'
Dieser Teil des Befehls ist mir geläufig. Dadurch wird die angegebene Webadresse mit dem mplayer abgespielt. Der Mplayer gibt zahlreiche Informationen aus unter anderm beispielsweise:
ICY Info: StreamTitle='Elton John - I'm Still Standing';StreamUrl='';
Hier soll dem Beispiel nur "Elton John - I'm Still Standing" entnommen und ausgegeben werden.
Bewirkt, dass eventuelle Fehler ins nichts umgelenkt werden. Dadurch werden eventuelle Fehlermeldungen unterdrückt. Dabei steht
Wenn ich statt /dev/null Ausgabe schreibe, wird eine Datei namens Ausgabe angelegt, in der alle Fehlermeldungen gespeichert werden:
Datei Ausgabe: mplayer: could not connect to socket mplayer: No such file or directory Failed to open LIRC support. You will not be able to use your remote control. Couldn't resolve name for AF_INET6: xapp2154659840c40000.f.l.i.lb.core-cdn.n$ Requested audio codec family [mpg123] (afm=mpg123) not available. Enable it at compilation.
Genau diese Informationen werden also umgeleitet und so deren Ausgabe in der Konsole verhindert.
statt nach /dev/null kann auch einfach nach 1 (STDOUT) umgeleitet werden. 2>&1 bewirkt diese Umleitung. Wenn wir im Nachgang die Ausgabe ohnehin filtern, hat dies die selbe Wirkung wie die Umlenkung nach /dev/null.
Gibt nun die verbleibende Ausgabe zu Verarbeitung an ein nachfolgend angegbenes Programm ab. In unserem Fall ist das awk.
Die interessante Zeile erhalte ich auch mit grep, allerdings ohne unwesentliche Teile der Zeile, wie ICY Info: StreamTitle=, herauszufiltern.
mplayer http://xapp2154659840c40000.f.l.i.lb.core-cdn.net/40000mb/live/app2154659840/w2153906178/live_de_192.mp3 2> /dev/null | grep Title
ergibt folgende Ausgabe:
ICY Info: StreamTitle='PI_Justin_Timberlake_not_a_bad_thing';StreamUrl='';
Hier gilt es also noch den wesentlichen Teil zu isolieren.
awk ist eine Art Progrogrammiersprache die auf jedem Linux-System automatisch zur Verfügung steht. In diesem Beispiel wird der gesuchte Text damit herausgefiltert. Ich möchte hier nicht weiter auf dieses sicherlich sehr interessante Thema eingehen und mich vielmehr speziel mit der Deutung der hier verwendeten Token beschäftigen.
Der Parameter -F kennzeichnet das folgende Zeichen als Trennoperator.
In unserem Beispiel folgt \'
Der Backslash Maskiert das '-Zeichen, so daß dieses dirkt verwendet wird und nicht seine Sonderfunktion als Stringgrenzkennzeichnung zum tragen kommt. Damit wird unsere Zeile dort getrennt, wo sich das '-Zeichen befindet.
ICY Info: StreamTitle='Elton John - I'm Still Standing';StreamUrl='';
ergibt:
Standing';StreamUrl='';
In diesem Beispiel zeigt sich, dass dies nur funktioniert, wenn das Sonderzeichen ' im Titel nicht vorkommt. Allerdings ist dieses Zeichen gerade in Musiktiteln doch recht häufig. Nachdem ich nun die Funktionsweise weitestgehend verstanden habe, sollte ich schauen, ob sich der Code nicht verbessern, lässt.
Teste Zeile auf '/StreamTitel/ {print $2}'
Zwischen den einfachen Anführungszeichen (') steht der eigentliche awk-code. Dieser besagt das nur Zeilen bearbeitet werden, die als Suchbegriff SteamTitle enthalten. Von diesen wird jeweils nur der 2. Stringteil gemäß der Trennung, wie unter Parameter -F\' beschrieben, ausgegeben.
Damit ist der Konsolenbefehl klar:
Allerdings funktioniert das nur, wenn der Musiktitel (der 2. Teil) nicht selbst das '-Zeichen enthält.
Bessere Alternative (Filtern zwischen StreamTitle=' und ';)
mplayer WebAdresse 2> /dev/null | grep -Po "(?<=Title=').*?(?=';)"
Ich such nun doch wieder mit Hilfe von grep. Dieses Mal allerdings unter Nutzung einer RegEx als Filter. Dabei bewirken die Komandozeilenparameter von grep folgendes:
Perlausdruck.
Teile jeweils auf einer eignen Zeile ausgegeben.
Durch die reguläre Expression:
"(?<=Title=').*?(?=';)"
wird nach allen Textteilen zwischen Titel=' und '; gesucht. Leider kann es vorkommen, dass anders als in meiner Beispielzeile, am Ende nicht: ';StreamUrl=''; steht, sondern nur '; . Das erschwert die Suche dahingehend, dass das Ende '; mehrfach in der Suchzeile vorkommen kann. Es muss also das ersten Auftreten als Endmarkierung genutzt werden. Im einzelnen bedeuten die Teile der RegEx folgendes:
als Startgrenze des Suchstrings.
der Startgrenze bis zur Endbegrenzung. Standartmäßig wird die letzte gefundene Endbegrenzung als Ende genutzt. Durch das ? dahinter wird das erste treffende Endemerkmal als Endbegrenzung genutzt.
Endbegrenzung des Suchstrings.
Um Text zwischen zwei Markierungen zu filtern kann man sich also folgende reguläre Expression merken:
(?<=WAS_STEHT_VOR_DEM_SUCHWORT).*?(?=WAS_STEHT_HINTER_DEM_SUCHWORT)
Da es doch etwas kompliziert wäre für jeden Internetradiostream die komplette Befehlzzeile zu schreiben, soll ein Script mit einer Menüauswahl diesen Job übernehmen. Es soll die verschiedenen Radiosender anbieten und den Befehl mit der Stream-URL des gewählten Senders absetzen und nach dem Beenden des Abspielens wieder zur Menüauswahl zurückkehren.
declare -A SENDER SENDER["Hannover Radio"]="http://xapp2154659840c40000.f.l.i.lb.core-cdn.net/40000mb/live/app2154659840/w2153906178/live_de_192.mp3" SENDER["Jamado Best of"]="http://streaming.radionomy.com/JamBestOf" SENDER["89.0 RTL"]="http://mp3.89.0rtl.de:80/" SENDER["sunshine live radio"]="http://stream.hoerradar.de/sunshinelive-mp3-128?sABC=50n29s80#0##cnegareighare" SENDER["Eurovision"]="http://listen.radionomy.com/songcontest-radio" SENDER["Einslive"]="http://gffstream.ic.llnwd.net/stream/gffstream_stream_wdr_einslive_a" SENDER["RTL Radio - Die besten Hits aller Zeiten"]="http://81.92.237.123:8080/listen.pls" EINTRAEGE=("NULL" "${!SENDER[@]}") menu() { clear echo "Maiks Konsolen-Webradio" echo "***********************" NR=0 for i in "${!SENDER[@]}"; do ((NR++)) [[ "${choices[$NR]}" ]] && Klammer=">>" || Klammer=") " printf "%3d%s%s%s\n" $NR "$Klammer" "${i}" "${choices[$NR]:- }" done [[ "$msg" ]] && echo -e "$msg"; : } clearMarke() { for i in "${!choices[@]}"; do choices[i]="" done } Anzeige() { [[ "${choices[num]}" ]] && choices[num]="" || choices[num]="<<" menu } prompt="Wähle einen Sender aus: (ENTER zum Beenden)" while menu && read -rp "$prompt" num && [[ "$num" ]]; do [[ "$num" != *[![:digit:]]* ]] && (( num > 0 && num <= ${#SENDER[@]} )) || { clear msg="Invalid option: $num"; continue } msg="${EINTRAEGE[num]} wird eingeschaltet. \n(mit 'q' oder ENTER zur Auswahl zurück)" clearMarke Anzeige URL=${SENDER[${EINTRAEGE[num]}]} mplayer ${URL} 2> /dev/null | grep -Po "(?<=Title=').*?(?=';)" clearMarke msg="" done echo "Das Radio wird ausgeschaltet." echo "Bye!"
declare -A SENDER SENDER["Hannover Radio"]="http://xapp2154659840c40000.f.l.i.lb.core-cdn.net/40000mb/live/app2154659840/w2153906178/live_de_192.mp3" SENDER["Jamado Best of"]="http://streaming.radionomy.com/JamBestOf" SENDER["89.0 RTL"]="http://mp3.89.0rtl.de:80/" SENDER["sunshine liveradio"]="http://stream.hoerradar.de/sunshinelive-mp3-128?sABC=50n29s80#0##cnegareighare" SENDER["Eurovision"]="http://listen.radionomy.com/songcontest-radio" SENDER["Einslive"]="http://gffstream.ic.llnwd.net/stream/gffstream_stream_wdr_einslive_a" SENDER["RTL Radio - Die besten Hits aller Zeiten"]="http://81.92.237.123:8080/listen.pls" EINTRAEGE=("NULL" "${!SENDER[@]}")
Mit declare -A wird ein sogenanntes assoziatives Array erzeugt, bei dem einem bestimmten Schlüsselwort ein Wert zugeordnet werden kann. Ich habe dem Array dem Namen Sender gegeben, wobei der Sendernamen als Schlüssel und die Stream-URl als Wert zugeordnet wird. Diese Vorgehensweise hatte ich der Zeitschrift Linux User entnommen. Sie hat den Vorteil, dass mann leicht Weitere Sender hinzufügen und auch wieder einen Sender entfernen kann.
Interessant ist hierbei folgendes :
Somit kann ich hier leicht ein Array meiner Menüpunkte in der Variable EINTRAEGE definieren. Da ich die Einträge als Zeichenketten benötige habe ich ${!SENDER[@]} in Anführungszeichen eingeschlossen. Damit der erste Menüpunkt bei eins anfängt habe ich einen Wert als Nulltes Array vergeben. Auch in der Folge werde ich die Rückgabe des Arrays nutzen um eine Itteration zu ermöglichen.
menu() { clear echo "Maiks Konsolen-Webradio" echo "***********************" NR=0 for i in "${!SENDER[@]}"; do ((NR++)) [[ "${choices[$NR]}" ]] && Klammer=">>" || Klammer=") " printf "%3d%s%s%s\n" $NR "$Klammer" "${i}" "${choices[$NR]:- }" done [[ "$msg" ]] && echo -e "$msg"; : }
Hier habe ich jetzt die Funktion menu definiert. Diese soll nichts weiter machen als das Menü auf dem Bildschirm auszugeben. Zunächst lösche ich mit clear den Bildschirm. Mit den nächsten 2 Zeilen gebe ich die mit Sternchen unterstrichne Überschrift für mei Menü aus. Dann initialisiere ich die Variable NR mit Null. Diese soll in der Folge als Zähler für die Menüpunkte dienen. Mit der folgenden For-Schleife iteriere ich durch alle Sendernamen. Im Array ${choices[$NR]} speichere ich später für jeden Menüpunkt eine Markierung für die Auswahl. Diese ist leer, wenn der Menüpunkt nicht ausgewählt ist und enthält ein "<<", wenn der Menüpunkt aktiviert ist.
Interesantes IF-Konstrukt :
[[ "${choices[$NR]}" ]] && Klammer=">>" || Klammer=") "
Prüft, ob die erste Variable einen Wert hat. Wenn ja wird der Teil hinter && ausgeführt, sonnst der Teil hinter ||. Dadurch deffiniere ich die Variable Klammer je nach dem, ob der Menüpunkt aktiv ist oder nicht. Klammer soll die Zeichen enthalten, die der Nummer des Menüpunktes folgen. Im Standard ')', bei ausgewähltem Menüpunkt aber '>>'.
In der folgenden Zeile erfolgt die Ausgabe mit dem printf -Befehl. Zunächst wird direkt hinter dem befehl die eigentlich Ausgabeformatierung angegeben. %3d heist das eine Zahl mit bis zu 3 Stellen ausgegeben wird. Hier wird der jeweilige wert von NR angegeben. Die folgenden 3 mal %s zeigen die Werte von Klammer, i mit dem Sendernamen und choices(NR) (leer oder <<) an.
Der Schleife folgt jetzt wieder das oben vorgestellte If-Konstrukt. Dieses Mal ohne den Sonnst-Teil
[ [ "$msg" ] ] && echo -e "msg"; :
Wenn also die Variable $msg einen Wert enthält, wird dieser ausgegeben. Der Parameter -e ermöglicht ggf. Maskierung mittels Schrägstrich anzugeben. Damit wird ermöglicht, dass Statusmeldungen ausgegeben werden, wenn dies nötig ist und sonnst keine Ausgabe erfolgt und somit eine Zeile weniger verbraucht wird.
Hier werden alle gesetzen Marken in choices gelöscht. Da die Funktion immer vor dem Setzen einer Marke aufgerufen wird, wird sichergestellt, dass immer nur ein Menüpunkt gleichzeitig als ausgewählt markiert sein kann. Hier wird wieder genutzt, dass mit ${!choices[@]} ein Array mit allen Schlüsseln des Arrays erzeugt wird, durch das man iterieren kann.
Diese Funktion setzt die Marke für den ausgewählten Menüpunkt und ruft die Ausgabe des Menüs auf. Hier hätte es eigentlich die reine Zuweisung gereicht, da ja immer nur ein Menüpunkt ausgewählt sein kann. Da es aber für meine Zwecke dennoch funktioniert und eine Interessante Möglichkeit zeigt werte zu alternieren, habe ich den Punkt unverändert belassen. So wird durch den Einsatz des IF-Konstrukts dafür gesorgt, dass der Variable ein Leerstring zugewiesen wird, wenn diese einen Wert enthält, anderen Falls der Wert "<<" gesetzt wird. Somit wird der Inhalt der Variable zwischen leer und "<<" alterniert.
Zunächst wird die Variable promt definiert, um diese in der Folge in der Ausgabe zu verwenden. Da dies nur einmal vorkommt, hätte mann dies auch weglassen und an der Stelle direkt die Zeichenkette direkt schreiben können. Allerdings erhöht die verwendete Variante deutlich die Lesbarkeit des Codes, weshalb ich es dabei belassen möchte.
Hier wird mit while die Schleife begonnen. Die zunächst das Menü ausgibt und dann über der read-Befehl die Eingabe des Nutzers in die Variable num einliest und direkt ausgibt, solange bis der Wert von num leer ist, also einfach ENTER gedrückt wurde. Die Parameter des read-Befehls bedeuten folgendes:
Plausibilitätsprüfung der Eingabe
Dann wird die Variable num überprüft, in der sich die Nutzereingabe befindet:
ist der Wert numerisch
[[ "$num" != *[![:digit:]]* ]]
ist der Wert größer Null und kleiner als die Zahl der Menüpunkte
(( num > 0 && num <= ${#SENDER[@]} ))
Dann mache nichts. Der Sonst-Zweig wird mit || eigeleitet und als Block mit {} umschlossen.
Innerhalb des Blocks wird mit clear der Bildschirminhalt gelöscht. Die Variable msg, die unsere Statusmeldung enthält wird mit dem Text "Ungültige Eingabe" und dem gerade getätigten Eingabewert gefüllt. Durch continue wird die Schleife direkt von vorn begonnen, wodurch das Menu angezeigt wird, in dem dann in der Statuszeile unsere Statusmeldung über die ungültige Eingabe steht und auf eine neue Eingabe des Nutzers gewartet wird.
Ist alles korrekt geht es dahinter damit weiter, dass
Wird während des Playbacks q oder ENTER gedrückt, wird der mplayer beendet und es geht entsprechend hinter dem Komando mit der Löschung der Auswahlmarkierung durch aufrufen der Funktion clearMarke und löschen der Statusvariable msg weiter. Dann beginnt die Auswahlschleife von vorn, wodurch jetzt wieder das Ausgangsmenü angezeigt wird.
Wird im Auswahlmenü einfach ENTER gedrückt, wodurch num leer bleibt, wird die Schleife verlassen und das Script nach Ausgabe von 'Das Radio wird ausgeschaltet.' und 'Bye' verlassen.